package net.kirillka.ebooks;

import org.eclipse.swt.*;
import org.eclipse.swt.browser.*;
import org.eclipse.swt.events.*;
import org.eclipse.swt.layout.*;
import org.eclipse.swt.widgets.*;
import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.*;

import java.io.*;
import java.net.*;
import java.util.Properties;

/**
 * @author Kirill Karpelson
 * 2008 Copyright Kirill Karpelson
 * Open Source. Do what you wish with this code.
 * 
 * Inspired by RasterBook application (http://www.a-i-studio.com/rasterbook/). Many thanks to the author. The only reason I needed to write this 
 * is because RasterBook (in my experience) didn't handle Russian language e-books properly. They displayed 
 * it as gibberish (that is, encoding wasn't correct) and there was no right-click menu (a.k.a. context menu)
 * available. 
 * 
 * That's why I wrote jRasterBook - the same functionality (or thereabouts) in Java, which does allow to 
 * change encoding via right-click menu (well, since the rendering is achieved by a IE widget, right-click 
 * menu has everything an IE page would have in a context menu.
 * 
 * Output will be saved in C:\Temp\book-N.jpg by default (where N is a number from 1 to the total number 
 * of pages your e-book contains on your device).
 * 
 * Relevant bookmarks:
 * 0) Original RasterBook app: http://www.a-i-studio.com/rasterbook/
 * 1) Java 6 API: http://java.sun.com/javase/6/docs/api/index.html
 * 2) SWT Browser Example: http://www.eclipse.org/articles/Article-SWT-browser-widget/DocumentationViewer.java
 * 3) SWT Toolbar Example: http://www.java2s.com/Code/Java/SWT-JFace-Eclipse/Thisclasscreatesacomplextoolbar.htm
 * 4) SWT FAQ - Screen Capture: http://www.eclipse.org/swt/faq.php#noeventfire
 * 5) Querying SWT Browser object: http://dev.eclipse.org/viewcvs/index.cgi/%7Echeckout%7E/org.eclipse.swt.snippets/src/org/eclipse/swt/snippets/Snippet160.java
 * 6) SWT Snippets: http://www.eclipse.org/swt/snippets
 * 7) Eclipse SDK Help: http://help.eclipse.org/help33/index.jsp?topic=/org.eclipse.rap.help/help/html/getting-started/examples.html
 * 8) Using alpha transparency with GC object: http://dev.eclipse.org/newslists/news.eclipse.platform.swt/msg21081.html
 * 9) Drawing text in SWT: http://www.eclipse.org/articles/Article-SWT-graphics/SWT_graphics.html
 * 
 * Known Bugs:
 * 
 * 1) jRasterBook has to have focus while doing the page capture. Do not switch to any other program. 
 * Imagine that your OS has become single-tasking. (This bug was only observed on Windows Vista with 
 * Sun JVM 1.6.0 - the only  environment that I have tried so far. Linux testing is pending).
 * 
 */
public class jRasterBook
{
	private Browser browser;
	private static int width = 0, height = 0;
	private Properties props = null;
	private int pages = 0;

	/**
	 * Constructor 
	 */
	public jRasterBook()
	{
		props = new Properties();
		try 
		{
			props.load(new FileInputStream(Constants.propertiesFile));
		}
		catch (FileNotFoundException e)
		{
			System.out.println("File " + Constants.propertiesFile + " not found. Using defaults.");
			e.printStackTrace();
		}
		catch (IOException e)
		{
			System.out.println("Could not read file " + Constants.propertiesFile + ". Using defaults.");
			e.printStackTrace();
		}
		
		// Overwrite default values
		if (props.containsKey("jrb.output.dir")) { Constants.outputDir = props.getProperty("jrb.output.dir"); } 
		if (props.containsKey("jrb.output.prefix")) { Constants.outputPrefix = props.getProperty("jrb.output.prefix"); }
		if (props.containsKey("jrb.debug")) { Constants.DEBUG = new Boolean(props.getProperty("jrb.debug")).booleanValue(); }
	}
	
	/**
	 * The application entry point
	 * 
	 * @param args the command line arguments
	 */
	public static void main(String[] args)
	{
		new jRasterBook().run();
	}

	/**
	 * Runs the application
	 */
	public void run()
	{
		Display display = new Display();
		Shell shell = new Shell(display);
		shell.setText("jRasterBook");

		createContents(shell);
		shell.open();
		while (!shell.isDisposed())
		{
			if (!display.readAndDispatch())
			{
				display.sleep();
			}
		}
		display.dispose();
	}


	/**
	 * Creates the window contents
	 * 
	 * @param shell the parent shell
	 */
	private void createContents(Shell shell)
	{  
		shell.setLayout(new GridLayout());
		Composite composite = new Composite(shell, SWT.NONE);
		GridData data = new GridData(GridData.FILL_HORIZONTAL); 
		composite.setLayoutData(data); 
		composite.setLayout(new GridLayout(2, true));

		// tool bar
		ToolBar toolBar = new ToolBar(composite, SWT.NONE);

		ToolItem openButton = new ToolItem(toolBar, SWT.PUSH);
		openButton.setText("&Open");
		//openButton.setImage(Constants.getImage(display, Constants.IMG_OPEN));
		openButton.setToolTipText("Opens html or txt file with the book text");

		ToolItem devList = new ToolItem(toolBar, SWT.DROP_DOWN);
		devList.setText("&Pick a device: "); //'P' is mnemonic!
		devList.setToolTipText("Pick predefined device dimensions here or pick 'Other/Manual' and enter dimensions yourself");
		DropdownSelectionListener devListener = new DropdownSelectionListener(devList);
		for (int i=0; i<Constants.devices.length; i++)
		{
			devListener.add(Constants.devices[i]);
		}
		devList.addSelectionListener(devListener);

		new ToolItem(toolBar, SWT.SEPARATOR);

		ToolItem captureButton = new ToolItem(toolBar, SWT.PUSH);
		captureButton.setText("&Capture Images!");
		captureButton.setToolTipText("Now that you have sized the window properly, generate images for each page");
		//captureButton.setImage(Constants.getImage(display, Constants.IMG_CAPTURE));

		new ToolItem(toolBar, SWT.SEPARATOR);

		ToolItem exitButton = new ToolItem(toolBar, SWT.PUSH);
		exitButton.setText("E&xit");
		//exitButton.setImage(Constants.getImage(display, Constants.IMG_EXIT));
		toolBar.pack();

		Composite comp = new Composite(shell, SWT.NONE);
		data = new GridData(SWT.FILL, SWT.FILL, true, true);
		comp.setLayoutData(data);
		comp.setLayout(new FillLayout());

		try
		{
			browser = new Browser(comp, SWT.NONE);
		}
		catch (SWTError e)
		{
			MessageBox messageBox = new MessageBox(shell, SWT.ICON_ERROR | SWT.OK);
			messageBox.setMessage("Closing application. The Browser could not be initialized.");
			messageBox.setText("Fatal error - application terminated");
			messageBox.open();
			System.exit(-1);
		}

		// listen to the changes of the browser's status bar
		browser.addStatusTextListener(new StatusTextListener()
		{
			public void changed(StatusTextEvent event)
			{
				browser.setData("scrollHeight", event.text);
			}
		});

		openButton.addSelectionListener(new SelectionAdapter()
		{
			public void widgetSelected(SelectionEvent event)
			{
				FileDialog dialog = new FileDialog(event.display.getActiveShell());
				String filename = dialog.open();
				if (filename == null) return;
				File file = new File(filename);
				String url="";
				try
				{
					url = file.toURI().toURL().toString();
				} 
				catch (MalformedURLException ex)
				{
					System.out.println("Can not get the URL for [" + filename + "]!");
				}
				browser.setUrl(url);
			}  
		});

		captureButton.addListener(SWT.Selection, new Listener() 
		{
			public void handleEvent(Event e)
			{
				capture();
			}
		});

		exitButton.addListener(SWT.Selection, new Listener() 
		{
			public void handleEvent(Event e)
			{
				System.exit(0);
			}
		});
	}


	/**
	 * Change the dimensions of the windows depending on the selected hand-help device
	 */
	public static void setGeometry(Shell s, String g)
	{
		if (Constants.devMap.get(g) != null)
		{
			height = Constants.devMap.get(g).height;
			width = Constants.devMap.get(g).width;
		}
		// else manual
		s.setSize(width, height);
	}

	/*
	 * Capture each screen of the window as an image
	 */
	public void capture()
	{		
		//browser.execute("window.scrollByPages(1)");
		//browser.execute("alert('Size: ' + document.body.clientWidth + 'x' + document.body.clientHeight)");
		//browser.execute("alert('Scroll: ' + document.body.scrollLeft + 'x' + document.body.scrollTop)");
		//browser.execute("alert(document.body.scrollHeight)"); // 
		//browser.execute("document.location.replace(document.URL + '?' + document.body.scrollHeight)");
		//browser.execute("alert(document.URL)");

		/*
		 * The communication between the browser widget and java code is 
		 * accomplished via the browser's status bar!
		 */

		boolean success = browser.execute("window.status=document.body.scrollHeight;");
		if (!success)
		{
			/* Script may fail or may not be supported on certain platforms. */
			System.out.println("Script was not executed.");
			return;
		}
		String sTotalHeight = (String)browser.getData("scrollHeight");
		if (Constants.DEBUG) { System.out.println("scroll height=" + sTotalHeight); }
		int totalHeight = new Integer(sTotalHeight).intValue();
		int pageDownPixels = (int) Math.round((height-Constants.VERT_ACCESSORIES) * Constants.PAGE_DOWN_RATIO); // 1 page-down in pixels
		pages = (int) Math.ceil(totalHeight/pageDownPixels); // each new page is, say, 420 - 12.5% overlap 
		pages++; // the last page will not be complete, so we just add one more as a simplest solution

		for (int i=1; i<=pages;i++)
		{
			// save the window as an image 
			printScreen(i);

			//page down
			success = browser.execute("window.scrollBy(0,"+ pageDownPixels +")");
			if (!success)
			{
				System.out.println("Page " + i + "/" + pages + " FAILED!");
			}
			int runningY = i*pageDownPixels; 
			if (Constants.DEBUG) { System.out.println("Page " + i + "/" + pages + "; y = " + runningY + "/" + totalHeight); }
		}
	}

	/**
	 * saves the browser as image file on disk
	 */
	private void printScreen(int i)
	{
		int digP = (int) Math.floor(1+Math.log10(pages)); // how many digits in the number of pages
		int digI = (int) Math.floor(1+Math.log10(i)); // how many digits in the current page number
		String pad = "";
		for (int j=0; j<(digP-digI); j++)
		{
			pad = pad + "0";
		}
		String pageNumber = pad + i;
		
		// Take the snapshot into a display Image
		Point size = browser.getSize();
		Image image = new Image(browser.getDisplay(), size.x-18, size.y-18); //get rid of the scroll bars
		GC gc = new GC(browser); 
		gc.copyArea(image, 0, 0);

		// draw page number on top
		//Font font = new Font(browser.getDisplay(),"Arial",80,SWT.BOLD | SWT.ITALIC);
		//gc.setForeground(browser.getDisplay().getSystemColor(SWT.COLOR_RED));
		//gc.setFont(font);
		//gc.drawText("PAGE " + pageNumber + "!!!",5,5, true);
		//gc.drawString("PAGE " + pageNumber + "!!!",5,105, true);
		
		// save image
		ImageLoader imageLoader = new ImageLoader();
		imageLoader.data = new ImageData[] {image.getImageData()};
		imageLoader.save(Constants.outputDir + "/" + Constants.outputPrefix + pageNumber + ".jpg",SWT.IMAGE_JPEG);

		// dispose
		image.dispose();
		gc.dispose();
		//font.dispose();
		
		// open the image again from the file on disk and write on top of it
		image = new Image(browser.getDisplay(),Constants.outputDir + "/" + Constants.outputPrefix + pageNumber + ".jpg");
		gc = new GC(image);
		
		// find the biggest font size for the page number string that will fit the page
		int fontSize = 400; // start from this size and go down
		Font font = new Font(browser.getDisplay(),"Arial",fontSize,SWT.BOLD);
		gc.setFont(font);			
		Point p = gc.stringExtent(pageNumber);
		if (Constants.DEBUG) { System.out.println("size: (" + p.x + "," + p.y + ")"); }
		while(p.x>width-18 || p.y>height-18)
		{
			fontSize-=10; //decrement font size by 10
			font.dispose();
			font = new Font(browser.getDisplay(),"Arial",fontSize,SWT.NONE);  // create smaller font
			gc.setFont(font);
			p = gc.stringExtent(pageNumber); //measure the string size
			if (Constants.DEBUG) { System.out.println("size: (" + p.x + "," + p.y + ")"); }
		}
		
		gc.setForeground(browser.getDisplay().getSystemColor(SWT.COLOR_GRAY));
		gc.setAlpha(110); // 125 is semi-transparent, 100 is even more transparent than that
		gc.drawText(pageNumber,0,0,true);
		// save image
		imageLoader = new ImageLoader();
		imageLoader.data = new ImageData[] {image.getImageData()};
		imageLoader.save(Constants.outputDir + "/" + Constants.outputPrefix + pageNumber + ".jpg",SWT.IMAGE_JPEG);
		
		gc.dispose();
		image.dispose();
		font.dispose(); 

		//System.gc(); // this doesn't prevent freezing the java window and having all the remaining images the same.
		if (Constants.DEBUG) { System.out.println("Total mem after doing print screen and dispose: " + Runtime.getRuntime().totalMemory()); }
		if (Constants.DEBUG) { System.out.println("Free mem after doing print screen and dispose: " + Runtime.getRuntime().freeMemory()); }
	}

}




/**
 * This class provides the "drop down" functionality for our drop-down tool items.
 */
class DropdownSelectionListener extends SelectionAdapter
{
	private ToolItem dropdown;
	private Menu menu;

	/**
	 * Constructs a DropdownSelectionListener
	 * 
	 * @param dropdown the drop-down this listener belongs to
	 */
	public DropdownSelectionListener(ToolItem dropdown)
	{
		this.dropdown = dropdown;
		menu = new Menu(dropdown.getParent().getShell());
	}

	/**
	 * Adds an item to the drop-down list
	 * 
	 * @param item the item to add
	 */
	public void add(String item)
	{
		MenuItem menuItem = new MenuItem(menu, SWT.NONE);
		menuItem.setText(item);
		menuItem.addSelectionListener(new SelectionAdapter()
		{
			public void widgetSelected(SelectionEvent event)
			{
				MenuItem selected = (MenuItem) event.widget;
				dropdown.setText(selected.getText());

				// We need to set the dimensions properly
				Shell s = selected.getDisplay().getActiveShell();
				jRasterBook.setGeometry(s, selected.getText());
				// And if width & height are still 0, then we must have the user pick width and height manually!
			}
		});
	}

	/**
	 * Called when either the button itself or the drop-down arrow is clicked
	 * 
	 * @param event the event that triggered this call
	 */
	public void widgetSelected(SelectionEvent event)
	{
		// Actually, we changed this behavior a little: 
		// we show the drop-down whenever they press an 
		// arrow or the button. 

		// If they clicked the arrow, we show the list
		// if (event.detail == SWT.ARROW)
		// {
		// Determine where to put the drop-down list
		ToolItem item = (ToolItem) event.widget;
		Rectangle rect = item.getBounds();
		Point pt = item.getParent().toDisplay(new Point(rect.x, rect.y));
		menu.setLocation(pt.x, pt.y + rect.height);
		menu.setVisible(true);
		// }
		// else
		// {
		//	// They pushed the button; take appropriate action
		//	ToolBarComplex.showMessage(dropdown.getParent().getShell(), dropdown.getText() + " Pressed");
		// }
	}
}